/*
    Heart of Gold engine management system
    Copyright (C) 2004 Heart of Gold development team

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package com.heartOfGold.model;

import java.util.Iterator;


/**
 * @author Greg Feigenson
 * @version 1RC
 * 
 *          Module to translate Byte[] <-> NMEA-0183 Message Objects.
 * 
 *          Singleton implementation, used as a toolkit class. Parses messages
 *          and strings and checksums. Fails null, so that if you pass it bad
 *          messages, it will echo an error and return null. Null checking is
 *          instituted so that a parse call will never parse a null message.
 * 
 *          !!!!Addendum!!!! a null return from parseBytes() is not necessarily
 *          an error, it may just mean that the full message has not yet been
 *          received.
 */

public final class NMEA extends Formatter {
    private static NMEA instance;
    private String buffer;
    private int bufferSize;
    private char c;

    private NMEA() {
        buffer = new String();
        bufferSize = 0;
    }

    public static NMEA getNMEA() {
        if (instance == null) {
            instance = new NMEA();
        }
        return instance;
    }

    public Message parseBytes(final byte[] bytes, final int numBytes) {
        /*
         * bytes[] has a length of 1024 ALWAYS but only numBytes of actual data
         * numBytes is the actual number of bytes in it we put in there
         * 
         * This method is recursive by nature of dealing with burst
         * transmissions to build strings.
         */
        char c;
        if (numBytes == 0) { // make sure we're not working on null bytes
            System.out.println("Error: HogNMEA.parseBytes()"
                    + " called on 0 bytes");
            return null;
        }
        for (int i = 0; i < numBytes; i++) {
            // now add the new bytes to the string
            /*
             * bufferSize allows me to discard anything after *chksum on the
             * NMEA message
             * 
             * A "Complete" message looks like $CMD,ARG1,ARG2,ARGN*0H Where CMD
             * is your command, ARG1 -> ARGN are arguments and 0H is the
             * checksum.
             */
            c = (char) bytes[i];
            if (c == '*') {
                // message will be "complete" in 3 more chars
                bufferSize = buffer.length() + 3;
            }
            buffer += c;
            // FIXME did moving the initialization of
            // c from the if statement break this?
        }
        buffer = buffer.trim(); // trim off any whitespace
        // if the message isn't "complete"
        if ((bufferSize == 0) || (buffer.length() != bufferSize)) {
            return null;
        }
        // NMEA0183 must have a $ as first char, * as third to last
        if (!buffer.startsWith("$") || !(buffer.charAt(bufferSize - 3) == '*')) {
            System.out.println("HogNMEA.parseString() "
                    + "received bad message:  " + buffer); // bind to stderr?
            buffer = new String(); // stop buffer overflow
            return null;
        }

        // Init everything needed for checksumming/building message
        Message messageObject = new Message();
        String messageChecksum, ourChecksum, args;
        String[] commandArgs;
        int i;

        // Checksumming
        // grab *chksum
        messageChecksum = buffer.substring(bufferSize - 2, bufferSize);
        // Strings are 1 indexed, and don't want *chksum in our args
        args = buffer.substring(1, bufferSize - 3);
        ourChecksum = checksum(args); // run our checksumming algorithm
        if (!messageChecksum.equalsIgnoreCase(ourChecksum)) {
            System.out.println("Invalid checksum: " + messageChecksum
                    + " does not equal ours (" + ourChecksum + ")");
            buffer = new String(); // stop endless bad buffer overflow
            return null;
        }
        // seed the args - includes message ID as index 0
        commandArgs = args.split(",");
        messageObject.setID(commandArgs[0]); // nab the CMD
        // rest of the args are really args
        for (i = 1; i < commandArgs.length; i++) {
            messageObject.addArg(commandArgs[i]);
        }
        /* Clean out the buffer */
        buffer = new String();
        bufferSize = 0;
        return messageObject;
    }

    public byte[] parseMessage(final Message message) {
        int parsedLength;
        byte[] parsed;
        if (message == null) {
            System.out.println("Error: parseMessage() "
                    + "called on a null message");
            return null;
        }
        Iterator<?> args = message.getArgs();
        String sentence = new String(), arg;
        while (args.hasNext()) {
            arg = (String) args.next();
            if (arg.indexOf("$") != -1 || arg.indexOf("*") != -1) {
                System.out.println("Error: the argument " + arg
                        + " contains an invalid character (*$,)");
                return null;
            }
            sentence += ",";
            sentence += arg;
        }
        sentence = message.getID() + sentence;
        // NMEA-0183 specifies CRLF
        sentence = "$" + sentence + "*" + checksum(sentence) + "\r\n";
        // convert to byte array
        parsedLength = sentence.length();
        parsed = new byte[parsedLength];
        for (int i = 0; i < parsedLength; i++) {
            parsed[i] = (byte) (sentence.charAt(i));
        }
        return parsed;
    }
}
